#include "PageCache.hpp"
PageCache PageCache::_sInst;

// 获取一个K页的span
Span *PageCache::NewSpan(size_t k)
{
    assert(k > 0);
    // 大于128page直接向堆申请
    if (k > NPAGES - 1)
    {
        void *ptr = static_cast<char *>(
            ::mmap(nullptr,                // 让内核自己挑地址
                   (k << 12),              // 要映射的字节数，必须是页大小的整数倍 128K/8192=16
                   PROT_READ | PROT_WRITE, // 可读可写
                   MAP_PRIVATE | MAP_ANONYMOUS,
                   -1, 0));
        Span *span = _spanPool.New();

        span->_pageId = (PAGE_ID)ptr >> (PAGE_SHIFT);
        // cout<<"PageCache"<<((uintptr_t)ptr>>37)<<endl;
        // cout<<ptr<<endl;
        // cout<<span->_pageId<<endl;
        span->_n = k;
        _idSpanMap[span->_pageId] = span;
        return span;
    }

    // 先检查第K个桶里面有没有span
    if (!_spanLists[k].Empty())
    {
        Span *kSpan = _spanLists[k].PopFront();
        // 建立id和span的映射 方便central cache回收小块内存时，查找对应的span
        for (PAGE_ID i = 0; i > kSpan->_n; i++)
        {
            _idSpanMap[kSpan->_pageId + i] = kSpan;
        }
        return kSpan;
    }
    // 检查一下后面的桶里面有没有span 如果有将他进行切分
    for (size_t i = k + i; i < NPAGES; i++)
    {
        if (!_spanLists[i].Empty())
        {
            Span *nSpan = _spanLists[i].PopFront();
            Span *kSpan = _spanPool.New();
            kSpan->_pageId = nSpan->_pageId;
            kSpan->_n = k;
            kSpan->_n = k;

            nSpan->_pageId += k;
            nSpan->_n -= k;
            _spanLists[nSpan->_n].PushFront(nSpan);
            // 存储nspan的首位页号跟nspan的映射
            // 方便page cache回收内存时进行合并查找
            // 存首页
            _idSpanMap[nSpan->_pageId] = nSpan;
            // 存尾页
            _idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;

            // 建立id和span的映射，方便central cache回收小块内存时，查找对应的span
            for (PAGE_ID i = 0; i < kSpan->_n; i++)
            {
                _idSpanMap[kSpan->_pageId + i] = kSpan;
            }
            return kSpan;
        }
    }
    // 走到这个位置就说明后面没有大页的span了
    // 这时就去找堆要一个128页的span
    Span *bigSpan = _spanPool.New();
    void *ptr = static_cast<char *>(
        ::mmap(nullptr,                // 让内核自己挑地址
               ((NPAGES - 1) << 12),   // 要映射的字节数，必须是页大小的整数倍 128K/8192=16
               PROT_READ | PROT_WRITE, // 可读可写
               MAP_PRIVATE | MAP_ANONYMOUS,
               -1, 0));
    bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT; // 通过地址可以算得页号 每页4KB
    bigSpan->_n = NPAGES - 1;
    _spanLists[bigSpan->_n].PushFront(bigSpan);
    return bigSpan;
}
// 获取对象到span的映射
Span *PageCache::MapObjectToSpan(void *obj)
{
    PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);
    std::unique_lock<std::mutex> lock(_pageMtx); // 加锁保证线程安全
    auto ret = _idSpanMap.find(id);
    if (ret != _idSpanMap.end())
    {
        return ret->second;
    }
    else
    {
        return nullptr;
    }
    // return nullptr;
}
// 释放空闲span到pagecache 合并相邻的span
void PageCache::ReleaseSpanToPageCache(Span *span)
{
    // 大于128page的直接还给堆
    if (span->_n > NPAGES - 1)
    {
        void *ptr = (void *)(span->_pageId << PAGE_SHIFT);
        assert(ptr);
        assert(span->_n > 0);
        munmap(ptr, span->_n >> PAGE_SHIFT);
        _spanPool.Delete(span);
        return;
    }
    // 对span前后的页 尝试进行合并 缓解内存碎片问题
    while (1)
    {
        // 找前一页
        PAGE_ID prevId = span->_pageId - 1;

        auto ret = _idSpanMap.find(prevId);
        // 如果前面不连续 就不合并
        if (ret == _idSpanMap.end())
        {
            break;
        }
        // 前面相邻的页在使用 不合并
        Span *prevSpan = ret->second;
        if (prevSpan->_isUse == true)
        {
            break;
        }
        // 合并后超过128页的span没办法管理 不合并
        if (prevSpan->_n + span->_n > NPAGES - 1)
        {
            break;
        }

        // 合并
        span->_pageId = prevSpan->_pageId;
        span->_n += prevSpan->_n;
        _spanLists[prevSpan->_n].Erase(prevSpan);
        _spanPool.Delete(prevSpan);
        // 只析构C++对象本身 并把其挂到池子里
        // 只是把一个几十字节的‘管理小对象’回收到自己的池子里
    }
    // 向后合并
    while (1)
    {
        PAGE_ID nextId = span->_pageId + span->_n;
        auto ret = _idSpanMap.find(nextId);
        if (ret == _idSpanMap.end())
        {
            break;
        }
        Span *nextSpan = ret->second;
        if (nextSpan->_isUse == true)
        {
            break;
        }
        if (nextSpan->_n + span->_n > NPAGES - 1)
        {
            break;
        }

        span->_n += nextSpan->_n;
        _spanLists[nextSpan->_n].Erase(nextSpan);
        _spanPool.Delete(nextSpan);
    }
    _spanLists[span->_n].PushFront(span);
    span->_isUse = false;
    _idSpanMap[span->_pageId] = span;
    _idSpanMap[span->_pageId + span->_n - 1] = span;
}